package com.chenjiajin.interceptor;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.chenjiajin.adive.ApiResponseBody;
import com.chenjiajin.domain.MyLogger;
import com.chenjiajin.domain.PojoDemo;
import com.chenjiajin.mapper.MyLoggerMapper;
import com.chenjiajin.utils.RequestUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Component
public class LoggerInterceptor implements HandlerInterceptor {

    private static final String LOGGER_SEND_TIME = "_send_time";    // 请求开始时间标识

    private static final String LOGGER_ENTITY = "_logger_entity";   // 请求日志实体标识

    /**
     * 进入SpringMVC的Controller之前开始记录日志实体
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws IOException {

        //创建日志实体
        MyLogger myLogger = new MyLogger();

        //获取请求sessionId
        String sessionId = request.getRequestedSessionId();

        //请求路径
        String url = request.getRequestURI();

        //获取请求参数信息                                                   DisableCircularReferenceDetect解决重复引用    ,  WriteMapNullValue解决默认忽略值为 null 的属性
        String paramData = JSON.toJSONString(request.getParameterMap(), SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.WriteMapNullValue);

        //设置客户端ip
        myLogger.setClientIp(RequestUtil.getIPAddressByRequest(request));

        //设置请求方法
        myLogger.setMethod(request.getMethod());

        //设置请求类型（json|普通请求）
        myLogger.setType(RequestUtil.getRequestType(request));

        //设置请求参数内容json字符串
        myLogger.setParamData(paramData);

        //设置请求地址
        myLogger.setUri(url);

        //设置sessionId  如果没有设置session, sessionId默认为空
        myLogger.setSessionId(sessionId);

        //设置请求开始时间
        request.setAttribute(LOGGER_SEND_TIME, System.currentTimeMillis());

        //设置请求实体到request内，方便afterCompletion后置方法调用
        request.setAttribute(LOGGER_ENTITY, myLogger);

        return true;
    }

    /**
     * 渲染视图之后执行的方法
     */
    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) {

    }

    /**
     * 后置
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object o, Exception e) throws IOException {
        //获取请求错误码
        int status = response.getStatus();

        //当前时间
        long currentTime = System.currentTimeMillis();

        //请求开始时间
        long time = Long.parseLong(request.getAttribute(LOGGER_SEND_TIME).toString());

        //获取本次请求日志实体
        MyLogger myLogger = (MyLogger) request.getAttribute(LOGGER_ENTITY);

        //设置请求时间差
        myLogger.setTimeConsuming(Integer.parseInt((currentTime - time) + ""));

        //设置返回时间
        myLogger.setReturnTime(currentTime + "");

        //设置返回错误码
        myLogger.setHttpStatusCode(status + "");

        //response.
        //设置返回值
        //myLogger.setReturnData(JSON.toJSONString(request.getAttribute(LoggerUtils.LOGGER_RETURN), SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.WriteMapNullValue));
        // 拦截器不能这样使用，因为它无法获得执行方法的返回值。 您可以使用面向切面编程(AOP)在不更改任何现有代码的情况下实现您的目标。

        /*
          在进入controller之前, 发现带@RestController的类和带@ResponseBody的方法在被调用后response会直接写入输出流，
          在postHandle和afterCompletion这两个方法执行之前就已经把数据返回，
          导致这两个方法里面的response根本获取不到响应数据(也无法拿到头信息等)。
          https://www.cnblogs.com/zhangww/p/13720478.html


          response是采用缓存输出流的方式回写,当执行到HandlerInterceptor.afterCompletion时,大部分数据可能已经写回的客户端, 没有办法获得了.
          可以尝试用Servlet Filter, 把里面的response替换成自己的实现, 但要注意的是如果不用流,而是保留在内存里,那么以性能和内存都要小心处理才行.
          https://m.imooc.com/wenda/detail/558251
         */

        PojoDemo data = ApiResponseBody.returnData;
        String returnData = JSON.toJSONString(data);
        myLogger.setReturnData(returnData);

        //执行将日志写入数据库
        MyLoggerMapper mapper = getDAO(MyLoggerMapper.class, request);
        log.info("日志入库 = {}", myLogger);
        mapper.insert(myLogger);

        // 尝试拿到返回值
        //response.getWriter().println();   // getOutputStream() has already been called for this response


    }


    /**
     * 根据传入的类型获取spring管理的对应dao   直接使用Autowired注入也可以
     * 在过滤器，拦截器，监听里面获取容器对象的方法
     */
    private <T> T getDAO(Class<T> clazz, HttpServletRequest request) {
        BeanFactory factory = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
        return factory.getBean(clazz);
    }

}